/**
 * @file dialog.c
 * @author Yao Zongyou <yaozy2010@gmail.com>
 * @date 2010-12-28
 */

#include <windows.h>

#include "dialog.h"

/* Constants for menu IDs */
#define mnuFileExit   5000
#define mnuMenu2Foo   5001
#define mnuMenu2Bar   5002
#define mnuMenu2FUBAR 5003

int InitWndClass(HINSTANCE hInst);
void btnOK_Click(dialog_t *theDialog);
void mnuFileExit_Click(dialog_t *theDialog);
void mnuMenu2Foo_Click(dialog_t *theDialog);
void mnuMenu2Bar_Click(dialog_t *theDialog);
void mnuMenu2FUBAR_Click(dialog_t *theDialog);
LRESULT CALLBACK dialog_t_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int InitWndClass(HINSTANCE hInst) {
  WNDCLASSEX class;
  
  class.cbSize = sizeof(class);
  class.style = 0;
  class.lpfnWndProc = dialog_t_Proc;
  class.cbClsExtra = 0;
  /* IMPORTANT:  cbWndExtra must be set to DLGWINDOWEXTRA to allow us to
   *             safely use DefDlgProc instead of DefWindowProc in our
   *             window procedure. */
  class.cbWndExtra = DLGWINDOWEXTRA;
  class.hInstance = hInst;
  class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  class.hCursor = LoadCursor(NULL, IDC_ARROW);
  class.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
  class.lpszMenuName = NULL;
  class.lpszClassName = "dialog_t";
  class.hIconSm = class.hIcon;

  return RegisterClassEx(&class);
}

/* passing the dialog_t* to each event handler gives it access to all
 * other controls on the dialog (as well as any other variables you find
 * convenient to place there. */
void btnOK_Click(dialog_t *theDialog) {
  int txtLength = SendMessage(theDialog->txtEdit, WM_GETTEXTLENGTH, 0, 0) + 1;
  char *buffer = GlobalAlloc(GPTR, txtLength);
  SendMessage(theDialog->txtEdit, WM_GETTEXT, txtLength, (LPARAM) buffer);
  MessageBox(theDialog->window, buffer, "txtEdit.Text", MB_OK);
}


void mnuFileExit_Click(dialog_t *theDialog) {
  SendMessage(theDialog->window, WM_CLOSE, 0, 0);
}

void mnuMenu2Foo_Click(dialog_t *theDialog) {
  MessageBox(theDialog->window, "Foo!", "Foo!", MB_OK);
}

void mnuMenu2Bar_Click(dialog_t *theDialog) {
  MessageBox(theDialog->window, "Bar!", "Bar!", MB_OK);
}

void mnuMenu2FUBAR_Click(dialog_t *theDialog) {
    MessageBox(theDialog->window, "You know, you see Foo and Bar an "
        "awful lot in programs when the author can't be bothered to "
        "think up anything more clever.  Ever wonder where they come "
        "from?  Well, they're from FUBAR, a military acronym that "
        "stands for \"Fucked Up Beyond All Repair/Recognition\".", 
        "FUBAR!", MB_OK);
}

LRESULT CALLBACK dialog_t_Proc(HWND hwnd, UINT msg,
                              WPARAM wParam, LPARAM lParam) {
  /* To very slightly improve performance, you can only GetWindowLongPtr
   * when you actually need it (instead of during every handled message!).
   * This doesn't, however, affect performance noticably even on slow
   * machines.  It also makes the code much more readable to do it in this
   * one place. */
  dialog_t *theDialog = (dialog_t*) GetWindowLongPtr(hwnd, GWL_USERDATA);
  
  switch(msg) {
    case WM_COMMAND:
      if((HWND) lParam == theDialog->btnOK && HIWORD(wParam) == BN_CLICKED) {
        btnOK_Click(theDialog);
      } else {
        switch(LOWORD(wParam)) {
          case mnuFileExit:
            mnuFileExit_Click(theDialog);
            break;
          case mnuMenu2Foo:
            mnuMenu2Foo_Click(theDialog);
            break;
          case mnuMenu2Bar:
            mnuMenu2Bar_Click(theDialog);
            break;
          case mnuMenu2FUBAR:
            mnuMenu2FUBAR_Click(theDialog);
            break;
        }
      }
      break;
    case WM_CLOSE:
      DestroyWindow(hwnd);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      /* IMPORTANT:  Don't return TRUE here - that only works with real
       *             dialogs.  ;)  Instead, call DefDlgProc for the default
       *             dialog behaviour. */
      return DefDlgProc(hwnd, msg, wParam, lParam);
  }
  return 0;
}













/** 
 * This function is responsible for doing all the "housekeeping" involved
 * in setting up a dialog window.  It is the semantic equivalent of
 * a constructor, if dialog_t was a class instead of a struct 
 */
dialog_t * new_dialog(HINSTANCE hInst)
{
  /** 
   * The normal font a window has looks pretty ugly, so we're going
   * to SendMessage(WM_SETFONT) each of the child windows we create with
   * the DEFAULT_GUI_FONT, which is much more reasonable. 
   */
  HFONT guiFont = GetStockObject(DEFAULT_GUI_FONT);
  
  dialog_t *p_dialog = GlobalAlloc(GPTR, sizeof(dialog_t));
  
  /**
   * InitWndClass will register the dialog window class and return true
   * if that operation was successful. 
   */
  if (InitWndClass(hInst)) { 
    HMENU menu, sub;
    /* Create our window's menus.
     * It is important to note that these menus will be disposed
     * of when the window closes because they belong to the window.
     * If you create menus that don't belong to a window (context menus,
     * etc), you must dispose of them using DestroyMenu().*/
    menu = CreateMenu();
    sub = CreatePopupMenu();
    AppendMenu(sub, MF_STRING, mnuFileExit, "E&xit");
    AppendMenu(menu, MF_STRING|MF_POPUP, (UINT_PTR) sub, "&File");

    sub = CreatePopupMenu();
    AppendMenu(sub, MF_STRING, mnuMenu2Foo, "&Foo");
    AppendMenu(sub, MF_STRING, mnuMenu2Bar, "&Bar");
    AppendMenu(sub, MF_STRING, mnuMenu2FUBAR, "F&UBAR");
    AppendMenu(menu, MF_STRING|MF_POPUP, (UINT_PTR) sub, "&Menu2");

    /* In the following CreateWindow(Ex) calls, CreateWindowEx is only used
     * where there is a meaningful ExStyle to be attached to the window.
     * Otherwise, CreateWindow is used (which is indentical to calling
     * CreateWindowEx with 0 as its first param */
    
    /* WS_EX_CONTROLPARENT allows tabbing to work according the documentation,
     * and is necessary for dialog-like behavior.*/    
    p_dialog->window = CreateWindowEx(WS_EX_APPWINDOW|WS_EX_CONTROLPARENT, 
                                       "dialog_t", "!Dialog", 
                                       WS_OVERLAPPEDWINDOW,
                                       CW_USEDEFAULT, CW_USEDEFAULT, 
                                       400, 300, NULL, NULL, hInst, NULL);

    SetMenu(p_dialog->window, menu);
  
    /* These are the child controls.  Significant to note:
     * WS_CHILD - Required style for child controls.
     * WS_VISIBLE - Otherwise, the control won't show up!
     * WS_TABSTOP - If you want to be able to tab to and from the control,
     *              you must include this style.
     *
     * Also, arg 9 is a handle to the parent control (the window) */
     
    p_dialog->txtEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
                                        WS_CHILD|WS_VISIBLE|WS_BORDER|
                                        WS_TABSTOP|ES_AUTOHSCROLL, 
                                        10, 10, 100, 20, 
                                        p_dialog->window, NULL, hInst, NULL);
  
  
    p_dialog->btnOK = CreateWindow("BUTTON", "OK",
                                    WS_CHILD|WS_VISIBLE|WS_TABSTOP|
                                    BS_DEFPUSHBUTTON, 10, 40, 75, 25, 
                                    p_dialog->window, NULL, hInst, NULL);
  
    if(p_dialog->window && 
       p_dialog->txtEdit && 
        p_dialog->btnOK) { /* Ensures successful creation of all windows */
    
      /* Set the font of each child control */
      SendMessage(p_dialog->txtEdit, WM_SETFONT, (WPARAM) guiFont, 0);
      SendMessage(p_dialog->btnOK, WM_SETFONT, (WPARAM) guiFont, 0);

      /* IMPORTANT:  Each window supplies 4 bytes (int32/long) for use by
       *             the program at the beginning of the window structure.
       *             You can access this with Get/SetWindowLong with
       *             GWL_USERDATA as the nIndex parameter (second arg).
       *             In this case, I use it to store the pointer to our
       *             dialog_t struct so that we can access it across calls
       *             to our window procedure.  */

      /* Note:  To stay compatible with 64-bit versions of Windows, use
       *        Set/GetWindowLongPtr instead (which takes a LONG_PTR argument/
       *        return type.  Or, that's what MSDN tells us to do. */
      SetWindowLongPtr(p_dialog->window, GWL_USERDATA, (LONG_PTR)p_dialog);
    
      return p_dialog;
    }
  }
  /* If we got here, something went wrong.  Free the struct and return null. */
  GlobalFree(p_dialog);
  return NULL;  

}

/** 
 * Put any other cleanup code in here (allocated resources such as brushes,
 * etc. that have been stored in the struct). 
 */
void delete_dialog(dialog_t *p_dialog)
{
	GlobalFree(p_dialog);
}
